﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using ZOIL.Framework.Core;
using ZOIL.Framework.Presentation;

namespace ZOIL.Library.Components.Image
{
    //static vars for queue

    public class AsyncImage : System.Windows.Controls.Image
    {
        [DllImport("gdi32.dll")]
        private static extern bool DeleteObject(IntPtr hObject);

        private void AsyncImage_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue.Equals(true) && e.NewValue != e.OldValue)
            {
                AsyncLoadImage();
            }
            else if (!ApplicationIsClosing)
            {
                ReleaseImageSource();
            }
        }

        protected override void OnVisualParentChanged(DependencyObject oldParent)
        {
            base.OnVisualParentChanged(oldParent);
            //get ZComponent Parent if exists
            DependencyObject parent = Parent;
            while (!(parent is ZComponent) && parent is FrameworkElement)
            {
                parent = (parent as FrameworkElement).Parent;
            }
            if (parent is ZComponent)
            {
                _ParentZComponent = parent as ZComponent;
            }
            else
            {
                _ParentZComponent = null;
            }

            parentFrame = ZTreeHelper.TryFindParent<ZComponentFrame>(this);
        }

        private void AsyncLoadImage()
        {
            if (parentFrame != null && parentFrame.Visibility != Visibility.Visible)
            {
                return;
            }

            if (SourcePath != null)
            {
                _TaskQueue.EnqueueTask(new ImageLoadingTask(this, SourcePath, INITIAL_DECODED_PIXEL_WIDTH));
            }
        }

        public void LoadImage(String sourcePath, int decodedPixelWidth)
        {
            var uri = new Uri(sourcePath);

            //new way DOESNT WORK
            //using (System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(uri.AbsolutePath))
            //{
            //    IntPtr hBitmap = bitmap.GetHbitmap();

            //        BitmapSource bitmapSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
            //        hBitmap,
            //        IntPtr.Zero,
            //        Int32Rect.Empty,
            //        System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
            //        bitmapSource.Freeze();
            //    Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, new Action<ImageSource>(SetImageSource), bitmapSource);

            //    DeleteObject(hBitmap);
            //}

            //byte[] buffer = File.ReadAllBytes(uri.AbsolutePath);
            //MemoryStream mem = new MemoryStream(buffer);

            //if (OriginalWidth == null)
            //{
            //    BitmapFrame bitmapFrame = BitmapFrame.Create(uri);
            //    OriginalWidth = bitmapFrame.PixelWidth;
            //}

            if (OriginalWidth != null)
            {
                decodedPixelWidth = (int) Math.Min(decodedPixelWidth, (double) OriginalWidth);
            }

            var bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.DecodePixelWidth = decodedPixelWidth;
            bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
            bitmapImage.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
            bitmapImage.UriSource = uri;
            //bitmapImage.StreamSource = mem;
            bitmapImage.EndInit();
            bitmapImage.Freeze();

            //Console.WriteLine("Image_" + this.GetHashCode() + ": " + sourcePath + " " + decodedPixelWidth + " LOADED.");

            //dispatch
            if (bitmapImage != null && !IgnoreSourcePath)
            {
                Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, new Action<ImageSource>(SetImageSource), bitmapImage);
                //mem.Dispose();
            }
        }

        public void SetImageSource(ImageSource source)
        {
            //if (Source != null)
            //    ReleaseImageSource();
            Source = source;
            ActualDecodedPixelWidth = (source as BitmapImage).DecodePixelWidth;
            _visibleImages.Add(this);
            RaiseDraggingFinishedEvent();
            GC.Collect();
        }

        public void ReleaseImageSource()
        {
            if (!IgnoreSourcePath)
            {
                //Console.WriteLine("Image_" + this.GetHashCode() + ": " + "released image: path={0}", SourcePath);
                Source = null;
                _visibleImages.Remove(this);
                GC.Collect();
            }
        }

        #region Initialized Event

        public delegate void InitializedEventHandler(object sender, RoutedEventArgs e);

        public static readonly RoutedEvent InitializedEvent =
            EventManager.RegisterRoutedEvent("Initialized", RoutingStrategy.Bubble, typeof (InitializedEventHandler), typeof (AsyncImage));

        public new event InitializedEventHandler Initialized
        {
            add { AddHandler(InitializedEvent, value); }
            remove { RemoveHandler(InitializedEvent, value); }
        }

        public void RaiseDraggingFinishedEvent()
        {
            var args = new RoutedEventArgs(InitializedEvent)
                           {
                               Source = this
                           };
            RaiseEvent(args);
        }

        #endregion

        #region variables for queue

        //private static PerformanceCounter performance = new PerformanceCounter("Memory", "Available MBytes");
        //private static double percent = 0.25;
        //public static double capacity = 0;
        //public static double freeMemory = performance.NextValue();
        //public static double maxCapacity = freeMemory * percent;
        //private static Hashtable hashTable = new Hashtable();

        #endregion

        #region properties

        public static readonly DependencyProperty SourcePathProptery = DependencyProperty.Register(
            "SourcePath",
            typeof (String),
            typeof (AsyncImage),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, null)
            );

        public double? OriginalWidth = null;

        private ZComponent _ParentZComponent;
        private ZComponentFrame parentFrame;

        public String SourcePath
        {
            get { return (String) GetValue(SourcePathProptery); }
            set
            {
                SetValue(SourcePathProptery, value);
                OriginalWidth = null;
                if (Visibility == Visibility.Visible) //search for existing images in queue JB
                {
                    AsyncLoadImage();
                }
            }
        }

        public ZComponent ParentZComponent
        {
            get { return _ParentZComponent; }
        }

        public int ActualDecodedPixelWidth { get; set; }

        public bool IgnoreSourcePath { get; set; }

        #endregion

        #region static properties

        private static readonly ImageTaskQueue _TaskQueue;

        private static readonly HashSet<AsyncImage> _visibleImages;

        private static bool _applicationIsClosing;

        // public static bool ApplicationIsClosing = false;
        //public static int IMAGE_TASK_THREAD_NUMBER = Environment.ProcessorCount * 2;
        public static int IMAGE_TASK_THREAD_NUMBER = 1;
        public static int IMAGE_REFRESH_SLEEP_VALUE = 250;
        public static int INITIAL_DECODED_PIXEL_WIDTH = 200;
        public static int DELTA_DECODED_PIXEL_WIDTH = 300;

        public static ImageTaskQueue TaskQueue
        {
            get { return _TaskQueue; }
        }

        public static HashSet<AsyncImage> VisibleImages
        {
            get { return _visibleImages; }
        }

        public static bool ApplicationIsClosing
        {
            get { return _applicationIsClosing; }
            set
            {
                _applicationIsClosing = value;
                if (value)
                {
                    _TaskQueue.Dispose();
                }
            }
        }

        #endregion

        #region construct

        static AsyncImage()
        {
            _TaskQueue = new ImageTaskQueue(IMAGE_TASK_THREAD_NUMBER);
            _visibleImages = new HashSet<AsyncImage>();
            _TaskQueue.EnqueueTask(new AllImageRefreshTask());
        }

        public AsyncImage()
        {
            Loaded += AsyncImage_Loaded;
        }

        private void AsyncImage_Loaded(object sender, RoutedEventArgs e)
        {
            IsVisibleChanged += AsyncImage_IsVisibleChanged;
        }

        #endregion
    }

    #region async image loader

    public class AllImageRefreshTask : ImageTask
    {
        public void doImageTask()
        {
            try
            {
                AsyncImage[] copy = AsyncImage.VisibleImages.ToArray();

                bool animationActive = false;
                if (copy.Length > 0 && copy[0] != null)
                {
                    copy[0].ParentZComponent.Dispatcher.Invoke(DispatcherPriority.ApplicationIdle,
                                                               new Action(
                                                                   () =>
                                                                       {
                                                                           animationActive =
                                                                               copy[0].ParentZComponent.ParentInformationLandscape.Animation.IsActive;
                                                                       }));
                }

                if (!animationActive)
                {
                    foreach (AsyncImage image in copy)
                    {
                        if (image.IgnoreSourcePath)
                        {
                            continue;
                        }
                        if (image.ParentZComponent != null && image.IsVisible) //nur wenn bild auch im Sichtbereich ist
                        {
                            //dispatch properties
                            double renderedWidth = image.ActualDecodedPixelWidth;
                            String sourcePath = null;

                            if (!image.ParentZComponent.IsOffScreen)
                            {
                                image.ParentZComponent.Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, new Action
                                                                                                                 (
                                                                                                                 delegate
                                                                                                                     {
                                                                                                                         if (image.OriginalWidth !=
                                                                                                                             null)
                                                                                                                         {
                                                                                                                             renderedWidth =
                                                                                                                                 Math.Min(
                                                                                                                                     image
                                                                                                                                         .ParentZComponent
                                                                                                                                         .RenderedSize
                                                                                                                                         .Width,
                                                                                                                                     (double)
                                                                                                                                     image
                                                                                                                                         .OriginalWidth);
                                                                                                                         }
                                                                                                                         else
                                                                                                                         {
                                                                                                                             renderedWidth =
                                                                                                                                 image
                                                                                                                                     .ParentZComponent
                                                                                                                                     .RenderedSize
                                                                                                                                     .Width;
                                                                                                                         }
                                                                                                                         sourcePath = image.SourcePath;
                                                                                                                     }
                                                                                                                 ));
                                //calc decode pixel
                                double deltaDecode = renderedWidth - image.ActualDecodedPixelWidth;
                                if (Math.Abs(deltaDecode) > AsyncImage.DELTA_DECODED_PIXEL_WIDTH)
                                {
                                    image.ActualDecodedPixelWidth = (int) renderedWidth;
                                    AsyncImage.TaskQueue.EnqueueTask(new ImageRefreshTask(image, sourcePath, (int) renderedWidth, renderedWidth));
                                    //Console.WriteLine("Image_" + image.GetHashCode() + ": " + "NOW IN QUEUE " + sourcePath + " " + renderedWidth);
                                }
                            }
                            else
                            {
                                if (image.ActualDecodedPixelWidth != AsyncImage.INITIAL_DECODED_PIXEL_WIDTH)
                                {
                                    image.ParentZComponent.Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, new Action
                                                                                                                     (
                                                                                                                     delegate
                                                                                                                         {
                                                                                                                             renderedWidth =
                                                                                                                                 image
                                                                                                                                     .ParentZComponent
                                                                                                                                     .RenderedSize
                                                                                                                                     .Width;
                                                                                                                             sourcePath =
                                                                                                                                 image.SourcePath;
                                                                                                                         }
                                                                                                                     ));

                                    image.ActualDecodedPixelWidth = AsyncImage.INITIAL_DECODED_PIXEL_WIDTH;
                                    AsyncImage.TaskQueue.EnqueueTask(new ImageRefreshTask(image, sourcePath, AsyncImage.INITIAL_DECODED_PIXEL_WIDTH,
                                                                                          renderedWidth, true));
                                    //Console.WriteLine("OFFSCREEN Image_" + image.GetHashCode() + ": " + "BACK TO INITIAL " + sourcePath + " " + renderedWidth);
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Image_" + GetHashCode() + ": " + "Error refreshing Image: " + ex.Message);
            }
            //sleep and restart

            Thread.Sleep(AsyncImage.IMAGE_REFRESH_SLEEP_VALUE);
                //jede sekunde werden alle auf ihre decodePixelWidth überpgüft, ob diese  nicht der tatsächlichen entspricht
            // if (!AsyncImage.ApplicationIsClosing)
            AsyncImage.TaskQueue.EnqueueTask(this);
                //anschließend wird der task in die queue übergeben womit dieser task wieder und immer wieder ausgeführt wird
        }
    }

    public class ImageRefreshTask : ImageLoadingTask
    {
        private readonly double calledRenderedWidth;
        private readonly bool ignoreValidation;

        public ImageRefreshTask(AsyncImage image, String sourcePath, int decodedPixelWidth, double calledRenderedWidth)
            : base(image, sourcePath, decodedPixelWidth)
        {
            this.calledRenderedWidth = calledRenderedWidth;
            ignoreValidation = false;
        }

        public ImageRefreshTask(AsyncImage image, String sourcePath, int decodedPixelWidth, double calledRenderedWidth, bool ignoreValidation)
            : base(image, sourcePath, decodedPixelWidth)
        {
            this.calledRenderedWidth = calledRenderedWidth;
            this.ignoreValidation = ignoreValidation;
        }

        public override void doImageTask()
        {
            if (image.IgnoreSourcePath)
            {
                return;
            }

            bool isAnimation = false;
            double renderedWidth = calledRenderedWidth;
            var imageVisibility = Visibility.Visible;
            bool isOffscreen = false;
            image.ParentZComponent.Dispatcher.Invoke(DispatcherPriority.SystemIdle, new Action
                                                                                        (delegate
                                                                                            {
                                                                                                renderedWidth =
                                                                                                    image.ParentZComponent.RenderedSize.Width;
                                                                                                //imageVisibility = image.Visibility;
                                                                                                isAnimation =
                                                                                                    image.ParentZComponent.ParentInformationLandscape
                                                                                                         .Animation.IsActive;
                                                                                                isOffscreen = image.ParentZComponent.IsOffScreen;
                                                                                            }));

            if (!isAnimation)
            {
                if ((ignoreValidation && imageVisibility == Visibility.Visible)
                    || (calledRenderedWidth == renderedWidth && imageVisibility == Visibility.Visible && !isOffscreen))
                {
                    base.doImageTask();
                }
                //else
                //Console.WriteLine("Image_" + image.GetHashCode() + ": " + _sourcePath + " ABORT (called != rendered width or visibility is false or OFFSCREEN)");
            }
            else
            {
                AsyncImage.TaskQueue.EnqueueTask(this);
                //Console.WriteLine("Image_" + image.GetHashCode() + ": " + _sourcePath + " NOT LOADED BECAUSE OF ACTIVE ANIMATION");
            }
        }

        //public void LoadAfterAnimaton(object sender, ZLandscapeAnimationEventArgs e)
        //{
        //    base.doImageTask();
        //    e.Animation.Finished -= new ZLandscapeAnimation.LandscapeAnimationDelegate(LoadAfterAnimaton);
        //}
    }

    public class ImageLoadingTask : ImageTask
    {
        private readonly AsyncImage _image;
        protected int _decodedPixelWidth;
        protected String _sourcePath;

        public ImageLoadingTask(AsyncImage image, String sourcePath, int decodedPixelWidth)
        {
            _image = image;
            _sourcePath = sourcePath;
            _decodedPixelWidth = decodedPixelWidth;
        }

        public AsyncImage image
        {
            get { return _image; }
        }

        public virtual void doImageTask()
        {
            if (image.IgnoreSourcePath)
            {
                return;
            }
            try
            {
                image.LoadImage(_sourcePath, _decodedPixelWidth);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }

    public interface ImageTask
    {
        void doImageTask();
    }

    public class ImageTaskQueue : IDisposable
    {
        private readonly object locker = new object();
        private readonly Thread[] workers;
        public Queue<ImageTask> taskQ = new Queue<ImageTask>();

        public ImageTaskQueue(int threadCount)
        {
            workers = new Thread[threadCount];

            // Create and start a separate thread for each worker
            for (int i = 0; i < threadCount; i++)
            {
                workers[i] = new Thread(Consume)
                    {
                        IsBackground = true,
                        Priority = ThreadPriority.Lowest
                    };
                workers[i].Start();
            }
        }

        public void Dispose()
        {
            lock (locker)
            {
                taskQ.Clear();
                // Enqueue one null task per worker to make each exit.
                foreach (Thread worker in workers)
                {
                    EnqueueTask(null);
                }
            }
            //foreach (Thread worker in workers)
            //        worker.Join();  
        }

        public void EnqueueTask(ImageTask task)
        {
            lock (locker)
            {
                taskQ.Enqueue(task);
                Monitor.PulseAll(locker);
            }
        }

        private void Consume()
        {
            while (true)
            {
                ImageTask task;

                lock (locker)
                {
                    while (taskQ.Count == 0)
                    {
                        //if (AsyncImage.ApplicationIsClosing)
                        //    return;       

                        Monitor.Wait(locker);
                    }
                    task = taskQ.Dequeue();
                }
                if (task == null)
                {
                    break;
                }
                try
                {
                    task.doImageTask();
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex);
                }
            }
        }
    }

    #endregion
}